/*
 * Copyright (c) 2025 Huawei Technologies Co., Ltd.
 * openUBMC is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *         http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

#include <mc/exception.h>
#include <mc/log.h>

#include <dlfcn.h>

#include "device/driver.h"
#include "devmon/driver_abi.h"

namespace devmon {
device_driver::device_driver(const std::string& driver_name) : m_driver_name(driver_name) {
}

std::vector<driver_load_info> device_driver::s_load_info_table;

void device_driver::load(const std::string& driver_path) {
    auto start = std::chrono::steady_clock::now();

    driver_load_info info;
    info.success      = false;
    info.load_time_ms = 0.0;
    info.device_count = 0;

    ilog("Trying to load driver from: ${file}", ("file", m_driver_name));

    std::string driver_file_name = "lib" + m_driver_name + ".so";
    std::string driver_file      = driver_path + "/" + driver_file_name;
    info.driver_name             = driver_file_name;
    auto* driver_handle          = dlopen(driver_file.c_str(), RTLD_LAZY);
    if (driver_handle == nullptr) {
        std::string error_msg = dlerror();
        info.error_msg        = error_msg;
        info.success          = false;
        info.load_time_ms =
            std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start)
                .count();

        s_load_info_table.push_back(std::move(info));
        MC_THROW(mc::system_exception, "Failed to load driver: ${error}", ("error", error_msg));
    }

    m_driver_handle = driver_handle;

    try {
        register_driver();
        info.success      = true;
        info.device_count = m_devices.size();
    } catch (const std::exception& e) {
        info.error_msg = e.what();
    }

    info.load_time_ms =
        std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start).count();

    s_load_info_table.push_back(std::move(info));

    dlog("Driver ${name} loaded successfully", ("name", m_driver_name));
}

void device_driver::unload() {
    if (m_driver_handle == nullptr) {
        ilog("Driver ${name} not loaded", ("name", m_driver_name));
        return;
    }

    dlclose(m_driver_handle);
    m_driver_handle = nullptr;
}

void device_driver::register_driver() {
    register_device_driver_func func = reinterpret_cast<register_device_driver_func>(
        dlsym(m_driver_handle, "register_device_driver"));
    MC_ASSERT_THROW(func, mc::bad_function_call_exception,
                    "Failed to get export_device_driver function: ${error}", ("error", dlerror()));

    device_driver_t* devices       = nullptr;
    uint8_t          devices_count = 0;
    status_t         ret           = func(&devices, &devices_count);
    MC_ASSERT_THROW(ret == STATUS_OK, mc::bad_function_call_exception,
                    "Failed to get device driver manager: ${error}", ("error", ret));

    m_devices.clear();
    m_devices.reserve(devices_count);
    for (uint8_t i = 0; i < devices_count; i++) {
        m_devices.emplace_back(std::make_shared<device_driver_t>(devices[i]));
    }
}

device_driver_t& device_driver::get_device(std::string& device_name) {
    MC_ASSERT_THROW(!m_devices.empty(), mc::invalid_arg_exception,
                    "Device driver not loaded: ${name}", ("name", m_driver_name));

    for (auto& device : m_devices) {
        if (device->device_name == device_name) {
            return *device;
        }
    }

    MC_THROW(mc::invalid_arg_exception, "Device driver not found: ${name}", ("name", device_name));
}

const device_drivers& device_driver::get_devices() const {
    return m_devices;
}

void device_driver::set_device_driver(device_drivers* device_drivers) {
    m_devices = *device_drivers;
}

std::string_view device_driver::get_name() {
    return m_driver_name;
}

device_driver_factory& device_driver_factory::get_instance() {
    static device_driver_factory instance;
    return instance;
}

void device_driver_factory::set_driver_path(const std::string& driver_path) {
    m_driver_path = driver_path;
}

device_driver& device_driver_factory::get_device_driver(const std::string& driver_name) {
    auto it = m_drivers.find(driver_name);
    if (it != m_drivers.end()) {
        return *it->second;
    }

    try {
        driver_ptr driver = std::make_shared<device_driver>(driver_name);
        driver->load(m_driver_path);

        m_drivers.emplace(driver_name, driver);
        return *m_drivers[driver_name];
    } catch (const std::exception& e) {
        MC_THROW(mc::invalid_arg_exception, "${error}", ("error", e.what()));
    }
}

void device_driver_factory::set_device_driver(const std::string& driver_name,
                                              device_drivers*    device_drivers) {
    auto it = m_drivers.find(driver_name);
    if (it != m_drivers.end()) {
        it->second->set_device_driver(device_drivers);
        return;
    }
    driver_ptr driver = std::make_shared<device_driver>(driver_name);
    driver->set_device_driver(device_drivers);
    m_drivers.emplace(driver_name, driver);
}

void device_driver_factory::remove_device_driver(const std::string& driver_name) {
    auto it = m_drivers.find(driver_name);
    if (it != m_drivers.end()) {
        m_drivers.erase(it);
    }
}

} // namespace devmon